#pragma once
#include "DLSInfo.h"
#include "soundbank/Soundbank.h"
#include "yzrutil.h"
#include "collection/ArrayList.hpp"
#include "collection/HashMap.hpp"
#include "io/File.h"
#include "io/OutputStream.h"
#include "util/RIFFReader.h"
#include "util/RIFFWriter.h"

namespace yzrilyzr_soundbank{
	class DLSSample;
	class DLSInstrument;
	class DLSModulator;
	class DLSRegion;
	class DLSSampleOptions;

	class DLSID{
		public:
		int64_t i1;
		int32_t s1;
		int32_t s2;
		int32_t x1;
		int32_t x2;
		int32_t x3;
		int32_t x4;
		int32_t x5;
		int32_t x6;
		int32_t x7;
		int32_t x8;
		bool operator==(const DLSID & other)const{
			return i1 == other.i1 && s1 == other.s1 && s2 == other.s2 && x1 == other.x1 &&
				x2 == other.x2 && x3 == other.x3 && x4 == other.x4 && x5 == other.x5 &&
				x6 == other.x6 && x7 == other.x7 && x8 == other.x8;
		}
		static DLSID read(yzrilyzr_util::RIFFReader & riff){
			DLSID d;
			d.i1=riff.readUnsignedInt();
			d.s1=riff.readUnsignedShort();
			d.s2=riff.readUnsignedShort();
			d.x1=riff.readUnsignedByte();
			d.x2=riff.readUnsignedByte();
			d.x3=riff.readUnsignedByte();
			d.x4=riff.readUnsignedByte();
			d.x5=riff.readUnsignedByte();
			d.x6=riff.readUnsignedByte();
			d.x7=riff.readUnsignedByte();
			d.x8=riff.readUnsignedByte();
			return d;
		}
	};

	ECLASS(DLSSoundbank, public Soundbank){
		private:
		static constexpr int const DLS_CDL_AND=0x0001;
			/** X = X | Y */
		static constexpr int const DLS_CDL_OR=0x0002;
		/** X = X ^ Y */
		static constexpr int const DLS_CDL_XOR=0x0003;
		/** X = X + Y */
		static constexpr int const DLS_CDL_ADD=0x0004;
		/** X = X - Y */
		static constexpr int const DLS_CDL_SUBTRACT=0x0005;
		/** X = X * Y */
		static constexpr int const DLS_CDL_MULTIPLY=0x0006;
		/** X = X / Y */
		static constexpr int const DLS_CDL_DIVIDE=0x0007;
		/** X = X && Y */
		static constexpr int const DLS_CDL_LOGICAL_AND=0x0008;
		/** X = X || Y */
		static constexpr int const DLS_CDL_LOGICAL_OR=0x0009;
		/** X = (X < Y) */
		static constexpr int const DLS_CDL_LT=0x000A;
		/** X = (X <= Y) */
		static constexpr int const DLS_CDL_LE=0x000B;
		/** X = (X > Y) */
		static constexpr int const DLS_CDL_GT=0x000C;
		/** X = (X >= Y) */
		static constexpr int const DLS_CDL_GE=0x000D;
		/** X = (X == Y) */
		static constexpr int const DLS_CDL_EQ=0x000E;
		/** X = !X */
		static constexpr int const DLS_CDL_NOT=0x000F;
		/** 32-bit constant */
		static constexpr int const DLS_CDL_CONST=0x0010;
		/** 32-bit value returned from query */
		static constexpr int const DLS_CDL_QUERY=0x0011;
		/** 32-bit value returned from query */
		static constexpr int const DLS_CDL_QUERYSUPPORTED=0x0012;

		static constexpr DLSID DLSID_GMInHardware{0x178f2f24,
			0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12};
		static constexpr DLSID DLSID_GSInHardware{0x178f2f25,
			0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12};
		static constexpr DLSID DLSID_XGInHardware{0x178f2f26,
			0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12};
		static constexpr DLSID DLSID_SupportsDLS1{0x178f2f27,
			0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12};
		static constexpr DLSID DLSID_SupportsDLS2{0xf14599e5,
			0x4689, 0x11d2, 0xaf, 0xa6, 0x0, 0xaa, 0x0, 0x24, 0xd8, 0xb6};
		static constexpr DLSID DLSID_SampleMemorySize{0x178f2f28,
			0xc364, 0x11d1, 0xa7, 0x60, 0x00, 0x00, 0xf8, 0x75, 0xac, 0x12};
		static constexpr DLSID DLSID_ManufacturersID{0xb03e1181,
			0x8095, 0x11d2, 0xa1, 0xef, 0x0, 0x60, 0x8, 0x33, 0xdb, 0xd8};
		static constexpr DLSID DLSID_ProductID{0xb03e1182,
			0x8095, 0x11d2, 0xa1, 0xef, 0x0, 0x60, 0x8, 0x33, 0xdb, 0xd8};
		static constexpr DLSID DLSID_SamplePlaybackRate{0x2a91f713,
			0xa4bf, 0x11d2, 0xbb, 0xdf, 0x0, 0x60, 0x8, 0x33, 0xdb, 0xd8};

		int64_t major=-1;
		int64_t minor=-1;

		DLSInfo info;

		yzrilyzr_collection::ArrayList<std::shared_ptr<DLSInstrument>> instruments;
		yzrilyzr_collection::ArrayList<std::shared_ptr<DLSSample>> samples;

		bool largeFormat=false;
		yzrilyzr_io::File * sampleFile=nullptr;

		public:
		DLSSoundbank(){}
		~DLSSoundbank(){}

		DLSSoundbank(yzrilyzr_io::File & file);

		DLSSoundbank(yzrilyzr_io::InputStream & inputstream);

		private:
		void readSoundbank(yzrilyzr_io::InputStream & inputstream);

		bool cdlIsQuerySupported(DLSID & uuid);

		int64_t cdlQuery(DLSID & uuid);

		// Reading cdl-ck Chunk
		// "cdl " chunk can only appear inside : DLS,lart,lar2,rgn,rgn2
		bool readCdlChunk(yzrilyzr_util::RIFFReader & riff);
		void readInfoChunk(yzrilyzr_util::RIFFReader & riff);

		void readLinsChunk(yzrilyzr_util::RIFFReader & riff);

		void readInsChunk(yzrilyzr_util::RIFFReader & riff);

		void readArt1Chunk(yzrilyzr_collection::ArrayList< std::shared_ptr<DLSModulator>> &modulators, yzrilyzr_util::RIFFReader & riff);

		void readArt2Chunk(yzrilyzr_collection::ArrayList< std::shared_ptr<DLSModulator>> &modulators, yzrilyzr_util::RIFFReader & riff);

		yzrilyzr_collection::HashMap<DLSRegion *, int64_t> temp_rgnassign;

		bool readRgnChunk(DLSRegion & split, yzrilyzr_util::RIFFReader & riff);

		void readWsmpChunk(DLSSampleOptions & sampleOptions, yzrilyzr_util::RIFFReader & riff);

		void readInsInfoChunk(DLSInstrument & dlsinstrument, yzrilyzr_util::RIFFReader & riff);

		void readWvplChunk(yzrilyzr_util::RIFFReader & riff);

		void readWaveChunk(yzrilyzr_util::RIFFReader & riff);

		void readWaveInfoChunk(DLSSample & dlssample, yzrilyzr_util::RIFFReader & riff);

		public:
		void save(yzrilyzr_lang::String & name);

		void save(yzrilyzr_io::File & file);

		void save(yzrilyzr_io::OutputStream & out);

		private:
		void writeSoundbank(yzrilyzr_util::RIFFWriter & writer);

		void writeSample(yzrilyzr_util::RIFFWriter & writer, DLSSample sample);

		void writeInstruments(yzrilyzr_util::RIFFWriter & writer);

		void writeInstrument(yzrilyzr_util::RIFFWriter & writer, DLSInstrument instrument);
		void writeArticulators(yzrilyzr_util::RIFFWriter & writer,
							   yzrilyzr_collection::ArrayList< std::shared_ptr<DLSModulator>> modulators);

		void writeRegion(yzrilyzr_util::RIFFWriter & writer, DLSRegion region, int version);

		void writeSampleOptions(yzrilyzr_util::RIFFWriter & wsmp,
								DLSSampleOptions sampleoptions);
		void writeInfoStringChunk(yzrilyzr_util::RIFFWriter & writer,
								  const yzrilyzr_lang::String & name, const yzrilyzr_lang::String & value);

		void writeInfo(yzrilyzr_util::RIFFWriter & writer, DLSInfo info);
		public:
		DLSInfo getInfo(){
			return info;
		}

		yzrilyzr_lang::String getName(){
			return info.name;
		}

		yzrilyzr_lang::String getVersion(){
			return std::to_string(major) + "." + std::to_string(minor);
		}

		yzrilyzr_lang::String getVendor(){
			return info.engineers;
		}

		yzrilyzr_lang::String getDescription(){
			return info.comments;
		}

		void setName(yzrilyzr_lang::String & s){
			info.name=s;
		}

		void setVendor(yzrilyzr_lang::String & s){
			info.engineers=s;
		}

		void setDescription(yzrilyzr_lang::String & s){
			info.comments=s;
		}
		int64_t getMajor(){
			return major;
		}

		void setMajor(int64_t major){
			this->major=major;
		}

		int64_t getMinor(){
			return minor;
		}

		void setMinor(int64_t minor){
			this->minor=minor;
		}

		std::vector<std::shared_ptr<SoundbankResource>> getResources()override;

		std::vector<std::shared_ptr<Instrument>> getInstruments()override;

		yzrilyzr_array::Array < std::shared_ptr < DLSSample>> getSamples();
		std::shared_ptr < Instrument> getInstrument(int32_t program, int32_t bank)override;

		void addResource(std::shared_ptr < SoundbankResource > resource);

		void removeResource(std::shared_ptr < SoundbankResource> resource);
		void addInstrument(std::shared_ptr < DLSInstrument > resource);

		void removeInstrument(std::shared_ptr < DLSInstrument > resource);
	};
}